#include <Alting.h>

#include <pthread.h>
#include <csp_shared_types.h>
#include <ProcessComposition.h>
#include <Channel.h>
#include <ChannelManager.h>

#include <csp_debug.h>


#define ALTING_ENABLING 6789
#define ALTING_WAITING 89367

Alting::Alting(ProcessComposition *p){
	altingProcess = p;
	channelManager = ChannelManager::Instance();

	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init (&state_cv, NULL);

	state = ALTING_ENABLING;
}


Alting::~Alting(){}


void Alting::Setup(csp_channel_t *list_, int size_){

	guards = list_;
	n_guards = size_;

	highestPriority = 9999999; /* MAX number of guards.. */
	processIsDisabling = false;

	state = ALTING_ENABLING;
}


ProcessComposition* Alting::AquireWrite(int idx)
{
	//channel_link_t *ret = NULL;
	ProcessComposition *ret=NULL;

	printf("idx=%i tries..\n", idx);

	pthread_mutex_lock(&mutex);

	/* If we're enabling writing proces should yield
	 * insted of waiting.
	 * */
	if( ALTING_ENABLING == state ){
		pendingWrites = true;
		pthread_mutex_unlock(&mutex);
		return NULL;
	}

	ASSERT( ALTING_WAITING == state );

	if( idx < highestPriority ){
		highestPriority = idx;
	}

	printf("Alting: priority %i tried.\n" ,idx);

	/* Another (writing) process is disabling. Wait until it's done.
	 * */
	if( processIsDisabling ){

		pthread_cond_wait(&state_cv, &mutex);
		pthread_mutex_unlock(&mutex);

	}else{
		processIsDisabling = true;
		pthread_mutex_unlock(&mutex);
		
		/** TESTING: 
		#define WAIT 10000
		long tt1 = WAIT, tt2;
		while(tt1--){
			tt2=WAIT;
			while(tt2--){}
		}
		*/

		/* Begin disabling: we do that without checking on writing end because
		 * if a write is pending, the writing process will be blocked until 
		 * the conditional variable is signaled (by a broadcast). */
		Channel *channel;
		
		for(i=0; i < n_guards; i++){
			channel = channelManager->GetChannel(guards[i].type, guards[i].id);
			/* We don't lock channel while we're disabling - to avoid a deadlock. */
			channel->AltingDisable();
		}


		pthread_mutex_lock(&mutex);

		state = ALTING_ENABLING;

		/* Broadcast to waiting processes. */
		pthread_cond_broadcast(&state_cv);

		pthread_mutex_unlock(&mutex);
	}

	/* This process won. */
	if( idx == highestPriority ){
		printf("priority %i got access to alt\n", idx);
/*
		ret = (channel_link*) malloc( sizeof(channel_link_t) );
		ret->process = altingProcess;
*/
		ret = altingProcess;
		ret->channel_link.ls_addr = guards[idx].addr;
		ret->channel_link.return_value = idx;
	}

	return ret;
}

ProcessComposition* Alting::PriSelect()
{
	Channel *channel=NULL, *chosen=NULL;
	//channel_link_t *ret;
	ProcessComposition *ret;

	int chosenIdx = 0;

	pthread_mutex_lock(&mutex);
	state = ALTING_ENABLING;
	pthread_mutex_unlock(&mutex);


	for(i=0; i < n_guards; i++){
	/*
		printf("ALTING %i channel{op=%X, type=%X, id=%llx, addr=%X, nbytes=%i}\n", 
				i,
				guards[i].op , guards[i].type , guards[i].id , 
				guards[i].addr , guards[i].nbytes);
*/
		channel = channelManager->GetChannel(guards[i].type, guards[i].id);

		channel->Lock();
		if( channel->AltingEnable(this, i) ){
			chosen = channel;
			chosenIdx = i;
			printf("enabled channel ready pri=%i\n", i);
			pthread_mutex_lock(&mutex);
			goto channelReady;
		}
		channel->Unlock();

	}


	pthread_mutex_lock(&mutex);
	if( pendingWrites ){
		printf("pending writes\n");
		goto channelReady;
	}

	state = ALTING_WAITING;	
	pthread_mutex_unlock(&mutex);
	// if we reach here: no channel was ready and we wait.
	return NULL;



channelReady:
	pthread_mutex_unlock(&mutex); /* We always come from a locked state. */

	while( i-- ){ /* We go through list backwards. */
		channel = channelManager->GetChannel(guards[i].type, guards[i].id);

		channel->Lock();
		
		if( channel->AltingDisable() ){
			chosen->Unlock();  // Lower priority
			chosenIdx = i;
			printf("Lower pri=%i\n", i);
			chosen = channel;
		}else{
			channel->Unlock();
		}

	}
	
	/* Process needs to know what idx was chosen. */
	altingProcess->GetPCB()->op_ret_val = chosenIdx;

	ret = chosen->Accept();

	chosen->Unlock();


	return ret;
}

